#define NOB_IMPLEMENTATION
#include "nob.h"

#define DEFAULT_CC "cc"
#define DEFAULT_CXX "c++"

#define RAYLIB_DIR "./raylib/raylib-5.5/src"
#define NOB_CACHE ".nob-cache"

enum GuiType
{
    GUI_TYPE_NONE = 0,
    GUI_TYPE_RAYLIB,
    GUI_TYPE_TQT,
    GUI_TYPE_MAX
};

enum BuildType
{
    BUILD_TYPE_DEBUG = 0,
    BUILD_TYPE_RELEASE,
    BUILD_TYPE_MAX
};

struct BuildOptions
{
    enum GuiType gui_type;
    enum BuildType build_type;
    const char *cc;
    int jobs;
    bool need_rebuild;
};

static const char *global_default_cflags[] = {
    "-Wall", "-Wextra", "-Wconversion", "-Wformat=2", "-Wformat-security", "-Wno-unused-parameter"
};

static const char *global_cli_files_and_deps[][2] = {
    { "./src/common.c", "./src/common.o" },
    { "./src/log.c", "./src/log.o" },
    { "./src/ar.c", "./src/ar.o" },
    { "./src/ex.c", "./src/ex.o" },
    { "./src/extractor-cli.c", "./src/extractor-cli.o" }
};

static const char *global_raylib_files_and_deps[][2] = {
    { "./src/extractor-raylib.c", "./src/extractor-raylib.o" },
};

static const char *global_tqt_files_and_deps[][2] = {
    { "./src/extractor-tqt.cpp", "./src/extractor-tqt.o" },
};

void parse_options(struct BuildOptions *options, int argc, char **argv)
{
    enum GuiType gui_type = GUI_TYPE_NONE;
    enum BuildType build_type = BUILD_TYPE_DEBUG;

    for (int i = 1; i < argc; ++i)
    {
        if (strncmp("-gui:", argv[i], sizeof("-gui:")-1) == 0)
        {
            char *gui = strchr(argv[i], ':');
            if (strlen(gui) > 1)
            {
                ++gui;
                if (strcmp(gui, "raylib") == 0)
                {
                    gui_type = GUI_TYPE_RAYLIB;
                }
                else if (strcmp(gui, "tqt") == 0)
                {
                    gui_type = GUI_TYPE_TQT;
                    options->cc = DEFAULT_CXX;
                }
                else
                {
                    fprintf(stderr, "[ERROR] Unknown GUI backend: %s\n", gui);
                    abort();
                }
            }
            else
            {
                fputs("[ERROR] -gui expects a value.\n", stderr);
                abort();
            }
        }
        else if (strncmp("-j:", argv[i], 3) == 0)
        {
            char *jobs = strchr(argv[i], ':');
            if (strlen(jobs) > 1)
            {
                ++jobs;
                int num = atoi(jobs);
                if (num <= 0)
                {
                    fprintf(stderr, "[WARN] -j: expects positive number. Will use %d.\n", options->jobs);
                    continue;
                }
                options->jobs = num;
            }
            else
            {
                fprintf(stderr, "[WARN] -j: expects positive number. Will use %d.\n", options->jobs);
                continue;
            }
        }
        else if (strncmp("-release", argv[i], sizeof("-release")-1) == 0)
        {
            build_type = BUILD_TYPE_RELEASE;
        }
    }

    if (options->gui_type != gui_type)
        options->need_rebuild = true;
    if (options->build_type != build_type)
        options->need_rebuild = true;

    options->gui_type = gui_type;
    options->build_type = build_type;
}

void build_cli(const struct BuildOptions *options)
{
    Nob_Cmd cmd = { 0 };
    Nob_Procs procs = { 0 };
    bool needs_link = options->need_rebuild;
    
    for (size_t i = 0; i < NOB_ARRAY_LEN(global_cli_files_and_deps); ++i)
    {
        if (options->need_rebuild || nob_needs_rebuild1(global_cli_files_and_deps[i][1], global_cli_files_and_deps[i][0]))
        {
            needs_link = true;
            nob_cmd_append(&cmd, options->cc, "-std=c11", "-c", global_cli_files_and_deps[i][0], "-o", global_cli_files_and_deps[i][1]);
            nob_da_append_many(&cmd, global_default_cflags, NOB_ARRAY_LEN(global_default_cflags));

            if (strcmp("./src/ar.c", global_cli_files_and_deps[i][0]) == 0)
                nob_cmd_append(&cmd, "-I/usr/include/uchardet");

            if (options->build_type == BUILD_TYPE_RELEASE)
                nob_cmd_append(&cmd, "-DNDEBUG", "-UDEBUG", "-g", "-O2");
            else
                nob_cmd_append(&cmd, "-DDEBUG", "-UNDEBUG", "-g3", "-ggdb", "-O0");

            nob_procs_append_with_flush(&procs, nob_cmd_run_async(cmd), options->jobs);

            cmd.count = 0;
        }
    }

    cmd.count = 0;

    if (!nob_procs_wait(procs))
        abort();

    if (!needs_link && nob_file_exists("ex") == 1)
        return;

    nob_cmd_append(&cmd, options->cc, "-o", "ex");

    for (size_t i = 0; i < NOB_ARRAY_LEN(global_cli_files_and_deps); ++i)
    {
        nob_cmd_append(&cmd, global_cli_files_and_deps[i][1]);
    }

    nob_cmd_append(&cmd, "-larchive", "-luchardet");

    if (!nob_cmd_run_sync(cmd))
        abort();

    nob_da_free(procs);
    nob_cmd_free(cmd);
}

bool build_libraylib(const struct BuildOptions* options)
{
    char dir[PATH_MAX];;
    memset(dir, '\0', sizeof(dir));
    getcwd(dir, sizeof(dir));
    bool result = true;

    if (!nob_set_current_dir(RAYLIB_DIR))
    {
        nob_return_defer(false);
    }

    const char *raylib_files_and_deps[][2] = {
        { "rcore.c", "rcore.o" },
        { "rshapes.c", "rshapes.o" },
        { "rtext.c", "rtext.o" },
        { "rtextures.c", "rtextures.o" },
        { "utils.c", "utils.o" }
    };

    Nob_Procs procs = { 0 };
    Nob_Cmd cmd = { 0 };
    bool need_link = false;
    bool existing_archive = (nob_file_exists("libraylib.a") == 1);

    for (size_t i = 0; i < NOB_ARRAY_LEN(raylib_files_and_deps); ++i)
    {
        if (nob_needs_rebuild1(raylib_files_and_deps[i][1], raylib_files_and_deps[i][0]))
        {
            need_link = true;

            nob_cmd_append(&cmd, options->cc,
                           "-std=c11", "-DPLATFORM_DESKTOP_RGFW", "-fPIC",
                           "-I" RAYLIB_DIR "/external/glfw/include",
                           "-c", raylib_files_and_deps[i][0],
                           "-o", raylib_files_and_deps[i][1]);

            nob_procs_append_with_flush(&procs, nob_cmd_run_async_and_reset(&cmd), options->jobs);
        }
    }

    if (!nob_procs_wait(procs))
    {
        nob_return_defer(false);
    }

    if (!existing_archive || need_link)
    {
        nob_cmd_append(&cmd, "ar", "-crs", "libraylib.a");
        for (size_t i = 0; i < NOB_ARRAY_LEN(raylib_files_and_deps); ++i)
        {
            nob_cmd_append(&cmd, raylib_files_and_deps[i][1]);
        }

        if (!nob_cmd_run_sync(cmd))
        {
            nob_return_defer(false);
        }
    }

    nob_set_current_dir(dir);
    if (nob_file_exists("libraylib.a") == 0 || !existing_archive)
        nob_copy_file(RAYLIB_DIR "/libraylib.a", "libraylib.a");

    return result;

defer:
    nob_set_current_dir(dir);
    return result;
}
 
void build_raylib(const struct BuildOptions *options)
{
    if (!build_libraylib(options))
        abort();

    if (nob_file_exists("libraylib.a") != 1)
    {
        nob_log(NOB_ERROR, "Missing libraylib.a");
        abort();
    }
    
    Nob_Cmd cmd = { 0 };
    Nob_Procs procs = { 0 };
    bool needs_link = options->need_rebuild;

    for (size_t i = 0; i < NOB_ARRAY_LEN(global_cli_files_and_deps); ++i)
    {
        if (options->need_rebuild || nob_needs_rebuild1(global_cli_files_and_deps[i][1], global_cli_files_and_deps[i][0]))
        {
            needs_link = true;
            nob_cmd_append(&cmd, options->cc, "-std=c11", "-c", global_cli_files_and_deps[i][0], "-o", global_cli_files_and_deps[i][1]);
            nob_da_append_many(&cmd, global_default_cflags, NOB_ARRAY_LEN(global_default_cflags));

            if (strcmp("./src/ar.c", global_cli_files_and_deps[i][0]) == 0)
                nob_cmd_append(&cmd, "-I/usr/include/uchardet");
            else if (strcmp("./src/ex.c", global_cli_files_and_deps[i][0]) == 0)
                nob_cmd_append(&cmd, "-DEX_GUI");

            if (options->build_type == BUILD_TYPE_RELEASE)
                nob_cmd_append(&cmd, "-DNDEBUG", "-UDEBUG", "-g", "-O2");
            else
                nob_cmd_append(&cmd, "-DDEBUG", "-UNDEBUG", "-g3", "-ggdb", "-O0");

            nob_procs_append_with_flush(&procs, nob_cmd_run_async(cmd), options->jobs);

            cmd.count = 0;
        }
    }

    for (size_t i = 0; i < NOB_ARRAY_LEN(global_raylib_files_and_deps); ++i)
    {
        if (options->need_rebuild || nob_needs_rebuild1(global_raylib_files_and_deps[i][1], global_raylib_files_and_deps[i][0]))
        {
            needs_link = true;
            nob_cmd_append(&cmd, options->cc, "-std=c11", "-c", global_raylib_files_and_deps[i][0], "-o", global_raylib_files_and_deps[i][1]);
            nob_da_append_many(&cmd, global_default_cflags, NOB_ARRAY_LEN(global_default_cflags));
            nob_cmd_append(&cmd, "-I" RAYLIB_DIR);

            if (options->build_type == BUILD_TYPE_RELEASE)
                nob_cmd_append(&cmd, "-DNDEBUG", "-UDEBUG", "-g", "-O2");
            else
                nob_cmd_append(&cmd, "-DDEBUG", "-UNDEBUG", "-g3", "-ggdb", "-O0");

            nob_procs_append_with_flush(&procs, nob_cmd_run_async(cmd), options->jobs);

            cmd.count = 0;
        }
    }

    cmd.count = 0;

    if (!nob_procs_wait(procs))
        abort();

    if (!needs_link && nob_file_exists("ex"))
        return;

    nob_cmd_append(&cmd, options->cc, "-o", "ex");

    for (size_t i = 0; i < NOB_ARRAY_LEN(global_cli_files_and_deps); ++i)
        nob_cmd_append(&cmd, global_cli_files_and_deps[i][1]);
    for (size_t i = 0; i < NOB_ARRAY_LEN(global_raylib_files_and_deps); ++i)
        nob_cmd_append(&cmd, global_raylib_files_and_deps[i][1]);

    nob_cmd_append(&cmd, "libraylib.a", "-lm", "-larchive", "-luchardet", "-lX11", "-lXrandr", "-lGLX");

    if (!nob_cmd_run_sync(cmd))
        abort();
}

void build_tqt(const struct BuildOptions *options)
{
    Nob_Cmd cmd = { 0 };

    if (nob_needs_rebuild1("src/extractor-tqt.moc", "src/extractor-tqt.cpp"))
    {
        nob_cmd_append(&cmd, "tqmoc", "src/extractor-tqt.cpp", "-o", "src/extractor-tqt.moc");
        if (!nob_cmd_run_sync(cmd))
            abort();

        cmd.count = 0;
    }

    Nob_Procs procs = { 0 };
    bool needs_link = options->need_rebuild;

    for (size_t i = 0; i < NOB_ARRAY_LEN(global_cli_files_and_deps); ++i)
    {
        if (options->need_rebuild || nob_needs_rebuild1(global_cli_files_and_deps[i][1], global_cli_files_and_deps[i][0]))
        {
            needs_link = true;
            nob_cmd_append(&cmd, options->cc, "-std=c++11", "-c", global_cli_files_and_deps[i][0], "-o", global_cli_files_and_deps[i][1]);
            nob_da_append_many(&cmd, global_default_cflags, NOB_ARRAY_LEN(global_default_cflags));

            if (strcmp("./src/ar.c", global_cli_files_and_deps[i][0]) == 0)
                nob_cmd_append(&cmd, "-I/usr/include/uchardet");
            else if (strcmp("./src/ex.c", global_cli_files_and_deps[i][0]) == 0)
                nob_cmd_append(&cmd, "-DEX_GUI");

            if (options->build_type == BUILD_TYPE_RELEASE)
                nob_cmd_append(&cmd, "-DNDEBUG", "-UDEBUG", "-g", "-O2");
            else
                nob_cmd_append(&cmd, "-DDEBUG", "-UNDEBUG", "-g3", "-ggdb", "-O0");

            nob_procs_append_with_flush(&procs, nob_cmd_run_async(cmd), options->jobs);

            cmd.count = 0;
        }
    }

    for (size_t i = 0; i < NOB_ARRAY_LEN(global_tqt_files_and_deps); ++i)
    {
        if (options->need_rebuild || nob_needs_rebuild1(global_tqt_files_and_deps[i][1], global_tqt_files_and_deps[i][0]))
        {
            needs_link = true;
            nob_cmd_append(&cmd, options->cc, "-c", global_tqt_files_and_deps[i][0], "-o", global_tqt_files_and_deps[i][1]);
            nob_da_append_many(&cmd, global_default_cflags, NOB_ARRAY_LEN(global_default_cflags));
            nob_cmd_append(&cmd, "-I/usr/include/tqt3", "-DTQT_THREAD_SUPPORT", "-DTQT_NO_ASCII_CAST", "-DTQT_NO_STL", "-D_REENTRANT");

            if (options->build_type == BUILD_TYPE_RELEASE)
                nob_cmd_append(&cmd, "-DNDEBUG", "-UDEBUG", "-g", "-O2");
            else
                nob_cmd_append(&cmd, "-DDEBUG", "-UNDEBUG", "-g3", "-ggdb", "-O0");

            nob_procs_append_with_flush(&procs, nob_cmd_run_async(cmd), options->jobs);

            cmd.count = 0;
        }
    }

    cmd.count = 0;

    if (!nob_procs_wait(procs))
        abort();

    if (!needs_link && nob_file_exists("ex") == 1)
        return;

    nob_cmd_append(&cmd, options->cc, "-o", "ex");

    for (size_t i = 0; i < NOB_ARRAY_LEN(global_cli_files_and_deps); ++i)
    {
        nob_cmd_append(&cmd, global_cli_files_and_deps[i][1]);
    }

    for (size_t i = 0; i < NOB_ARRAY_LEN(global_tqt_files_and_deps); ++i)
    {
        nob_cmd_append(&cmd, global_tqt_files_and_deps[i][1]);
    }

    nob_cmd_append(&cmd, "-larchive", "-ltqt-mt", "-luchardet");

    if (!nob_cmd_run_sync(cmd))
        abort();

    nob_da_free(procs);
    nob_cmd_free(cmd);
}

void parse_previous_config(struct BuildOptions *options)
{
    // Defaults
    options->gui_type = GUI_TYPE_NONE;
    options->build_type = BUILD_TYPE_DEBUG;
    options->cc = DEFAULT_CC;
    options->jobs = 1;

    FILE *config = fopen(NOB_CACHE, "r");
    if (!config)
        return;

    char *line = NULL;
    size_t len = 0;
    size_t nread;

    while ((nread = getline(&line, &len, config)) != -1)
    {
        char *token = strtok(line, "=");
        if (!token)
        {
            nob_log(NOB_WARNING, "Invalid previous configuration line (no '='): %s", line);
            continue;
        }

        char *value = strtok(NULL, "=");
        if (!value)
        {
            nob_log(NOB_WARNING, "Invalid previous configuration line (no value): %s", line);
            continue;
        }

        if (strcmp(token, "gui_type") == 0)
        {
            int type = atoi(value);
            if (type >= GUI_TYPE_MAX || type < GUI_TYPE_NONE)
            {
                nob_log(NOB_WARNING, "Invalid previous configuration GuiType: %d", value);
                continue;
            }
            options->gui_type = type;
        }
        else if (strcmp(token, "build_type") == 0)
        {
            int type = atoi(value);
            if (type >= BUILD_TYPE_MAX || type < BUILD_TYPE_DEBUG)
            {
                nob_log(NOB_WARNING, "Invalid previous configuration BuildType: %d", value);
                continue;
            }
            options->build_type = type;
        }
    }
}

void write_config(const struct BuildOptions *options)
{
    FILE *config = fopen(NOB_CACHE, "w");
    if (!config)
    {
        perror("fopen");
        return;
    }

    fprintf(config, "gui_type=%d\n", options->gui_type);
    fprintf(config, "build_type=%d\n", options->build_type);

    fflush(config);
    fclose(config);
}

int main(int argc, char **argv)
{
    NOB_GO_REBUILD_URSELF(argc, argv);

    struct BuildOptions options;
    parse_previous_config(&options);
    parse_options(&options, argc, argv);

    switch (options.gui_type)
    {
    case GUI_TYPE_NONE:
        build_cli(&options);
        break;
    case GUI_TYPE_RAYLIB:
        build_raylib(&options);
        break;
    case GUI_TYPE_TQT:
        build_tqt(&options);
        break;
    default:
        fprintf(stderr, "[ERROR] Do not know how to build GUI for GuiType %d\n", options.gui_type);
        exit(1);
    }

    write_config(&options);

    return 0;
}
